package edu.whu.rose.clock.ssql.index;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.StringTokenizer;

import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.DatabaseException;

import edu.whu.rose.clock.ssql.util.SequentialStrArray;
import edu.whu.rose.clock.ssql.util.StringArrayBinding;

public class EdgeIndexCache {

	private int size;
	private HashMap<String, ArrayList<String>> cache;
	private IndexDB idb;
	private HashSet<String> leastVisited;
	private final int setSize = 1000;
	
	private boolean everBeenWritten = false;
	
	private long totalMissNum;
	private long totalHitNum;
	private long missNum;
	private long hitNum;
	
	public EdgeIndexCache(IndexDB idb, int size) {
		cache = new HashMap<String, ArrayList<String>>();
		leastVisited = new HashSet<String>();
		this.idb = idb;
		this.size = size;
	}
	
	public void initInsert(String key, String value) throws UnsupportedEncodingException, DatabaseException {
//		if (everBeenWritten) {
//			this.insert(key, value);
//			return;
//		}
		String lcKey = key.toLowerCase();
		ArrayList<String> data = cache.get(lcKey);
		if (data != null) {
			data.add(value);
			this.update1(lcKey);
			hitNum++;
		}
		else {
			data = new ArrayList<String>();
			data.add(value);
			cache.put(lcKey, data);
			this.update2(lcKey);
			missNum++;
		}
	}
	
//	public void insert(String key, String value) throws UnsupportedEncodingException, DatabaseException {
//		String lcKey = key.toLowerCase();
//		int id = new Integer(value).intValue();
//		SequentialIntArray data = cache.get(lcKey);
//		if (data != null) {
//			data.insert(id);
//			this.update1(lcKey);
//			hitNum++;
//		}
//		else {
//			data = getFromDB(lcKey);
//			if (data != null) {
//				data.insert(id);
//				cache.put(lcKey, data);
//			}
//			else {
//				data = new SequentialIntArray();
//				data.insert(id);
//				cache.put(lcKey, data);
//			}
//			this.update3(lcKey);
//			missNum++;
//		}
//	}
	
	public SequentialStrArray getFromDB(String key) throws UnsupportedEncodingException, DatabaseException {
		String list = idb.get(key);
		if (list != null) {
			SequentialStrArray result = new SequentialStrArray();
			StringTokenizer st = new StringTokenizer(list, ",");
			while (st.hasMoreTokens()) {
				result.insert(st.nextToken());
			}
			return result;
		}
		return null;
	}
	
	public void putIntoDB(String key, ArrayList<String> data) throws UnsupportedEncodingException, DatabaseException {
		DatabaseEntry keyword = new DatabaseEntry(key.getBytes("UTF-8"));
		DatabaseEntry list = new DatabaseEntry();
		StringArrayBinding sab = new StringArrayBinding();
		sab.objectToEntry(data.toArray(new String[data.size()]), list);
		
		idb.put(keyword, list);
	}
	
	public void update1(String key) {
		leastVisited.remove(key);
	}
	
	public void update2(String key) {
		if (leastVisited.size() < setSize ) {
			leastVisited.add(key);
		}
		if (cache.size() == size) {
			everBeenWritten = true;
			System.out.println("cache exhausted..................................");
		}
	}
	
	public void update3(String key) throws UnsupportedEncodingException, DatabaseException {
		if (leastVisited.size() > 0) {
			Iterator<String> it = leastVisited.iterator();
			String abandon = it.next();
			this.putIntoDB(abandon, cache.get(abandon));
			cache.remove(abandon);
			leastVisited.remove(abandon);
		}
		else {
			Iterator<String> it = cache.keySet().iterator();
			String abandon = it.next();
			this.putIntoDB(abandon, cache.get(abandon));
			cache.remove(abandon);
		}
		leastVisited.add(key);
	}
	
	public void flush() throws UnsupportedEncodingException, DatabaseException {
		Iterator<String> it = cache.keySet().iterator();
		int counter = 0;
		Date stime = new Date();
		Date etime = null;
		while (it.hasNext()) {
			String key = it.next();
			ArrayList<String> data = cache.get(key);
			this.putIntoDB(key, data);
			counter++;
			if (counter % 100 == 0) {
				etime = new Date();
				System.out.println(counter + "........................." + (etime.getTime() - stime.getTime()) + "ms");
				stime = etime;
			}
		}
	}
	
	public void statistic() {
		System.out.println("Cache Hit Rate: " + ((double)hitNum / ((double)hitNum + (double)missNum)));
		totalHitNum += hitNum;
		hitNum = 0;
		totalMissNum += missNum;
		missNum = 0;
		System.out.println("Cache Hit Rate: " + ((double)totalHitNum / ((double)totalHitNum + (double)totalMissNum)));
	}

}
